using System;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using DotNetMatrix;

namespace bpcsharp
{
    internal class bpcsharp
    {
        //static String cesta = @"c:\jama\";
        static String cesta = @"../";

        public static HiPerfTimer hpt;
        
        private static GeneralMatrix A;
        private static GeneralMatrix B;
        private static GeneralMatrix X;
        private static GeneralMatrix U;
        private static GeneralMatrix V;
        private static GeneralMatrix Z;

        private static double[][] pole2D;
        private static double[] pole1Da;
        private static double[] pole1Db;

        private static int random = 2; 
        
        // 0 3x3
        // 1 random
        // 2 file

        private static void Main(string[] args)
        {
            Thread.CurrentThread.Priority = ThreadPriority.Highest;

            hpt = new HiPerfTimer();
            
            Console.WriteLine("Start C# benchmark");
            ////A=nacti("A");///////////
            int m, n;

            switch (random)
            {
                case 1:
                    m = 800;
                    n = 800;

                    hpt.Start();
                    A = GeneralMatrix.Random(m, n);
                    hpt.Stop();
                    Console.WriteLine("Cas vytvoreni matice:{0} ms", hpt.Duration);

                    B = GeneralMatrix.Random(m, n);
                    break;

                case 0:
                    double[][] vals = {
                                          new double[] {1.0, 2.0, 2.0},
                                          new double[] {4.0, 5.0, 6.0},
                                          new double[] {7.0, 8.0, 9.0}
                                      };
                    double[][] valss = {
                                           new double[] {4.0, 5.0, 6.0},
                                           new double[] {2.0, 8.0, 9.0},
                                           new double[] {3.0, 7.0, 8.0}
                                       };
                    A = new GeneralMatrix(vals);
                    B = new GeneralMatrix(valss);
                    break;
                    
                case 2:
                    A = nacti("A");
                    B = nacti("B");
                    break;
            }
            m = A.RowDimension;
            n = A.ColumnDimension;
            GeneralMatrix timeM = new GeneralMatrix(27, 1);

            hpt.Start();
            X = GeneralMatrix.Random(m, n);
            hpt.Stop();
            Console.WriteLine("Cas vytvoreni matice:{0} ms", hpt.Duration);
            timeM.SetElement(1, 0, hpt.Duration);


            hpt.Start();
            X = GeneralMatrix.Identity(m, n);
            hpt.Stop();
            Console.WriteLine("Cas 1 na diagonale:" + hpt.Duration + " ms");
            timeM.SetElement(2, 0, hpt.Duration);
            fileWrite("eye", X);

            // copy OK
            hpt.Start();
            X = A.Copy();
            hpt.Stop();
            Console.WriteLine("Cas kopirovani matice:" + hpt.Duration + " ms");
            timeM.SetElement(3, 0, hpt.Duration);
            //vypis(X);
            fileWrite("copy", X);


            // getArray
            hpt.Start();
            pole2D = A.Array;
            hpt.Stop();
            Console.WriteLine("Cas prevedeni matice na pole:" + hpt.Duration + " ms");
            timeM.SetElement(4, 0, hpt.Duration);

            // get
            hpt.Start();
            double prvek = A.GetElement(2, 2);
            hpt.Stop();
            Console.WriteLine("Cas ziskani prvku v matici:" + hpt.Duration + " ms");
            Console.WriteLine(prvek);

            // getMatrix - Initial row index,Final row index,Initial column index,
            //Final columnindex
            hpt.Start();
            X = A.GetMatrix(0, m/2 -1, 0, n/2 -1);
            hpt.Stop();
            Console.WriteLine("Cas ziskani casti matice:" + hpt.Duration + " ms");
            timeM.SetElement(5, 0, hpt.Duration);
            fileWriteDouble("part", X);


            // Set a single element.
            X = A.Copy();
            hpt.Start();
            X.SetElement(1, 1, 4);
            hpt.Stop();
            Console.WriteLine("Cas nastaveni prvku v matici:" + hpt.Duration + " ms");

            // A'
            hpt.Start();
            X = A.Transpose();
            hpt.Stop();
            Console.WriteLine("Cas transponovani matice:" + hpt.Duration + " ms");
            timeM.SetElement(6, 0, hpt.Duration);
            fileWrite("tra", X);

            // Norm1 - maximalni suma radku
            hpt.Start();
            double anorm1 = A.Norm1();
            hpt.Stop();
            Console.WriteLine("Cas norm1:" + hpt.Duration + " ms");
            timeM.SetElement(7, 0, hpt.Duration);
            Console.WriteLine(anorm1);
            fileWrite("norm1", anorm1);

            // Maximum singular value
            hpt.Start();
            double anorm2 = A.Norm2();
            hpt.Stop();
            Console.WriteLine("Cas norm2:" + hpt.Duration + " ms");
            timeM.SetElement(8, 0, hpt.Duration);
            Console.WriteLine(anorm2);
            fileWrite("norm2", anorm2);

            // NormInf - maximalni suma sloupce
            hpt.Start();
            double anormInf = A.NormInf();
            hpt.Stop();
            Console.WriteLine("Cas normInf:" + hpt.Duration + " ms");
            timeM.SetElement(9, 0, hpt.Duration);
            Console.WriteLine(anormInf);
            fileWrite("normi", anormInf);

            // Frobenius norm, sqrt(sum(diag(X'*X))).
            hpt.Start();
            double anormF = A.NormF();
            hpt.Stop();
            Console.WriteLine("Cas normF:" + hpt.Duration + " ms");
            timeM.SetElement(10, 0, hpt.Duration);
            Console.WriteLine(anormF);
            fileWrite("normf", anormF);

            // -A
            hpt.Start();
            X = A.UnaryMinus();
            hpt.Stop();
            Console.WriteLine("Cas unary minus:" + hpt.Duration + " ms");
            timeM.SetElement(11, 0, hpt.Duration);
            fileWrite("uminus", X);

            // plus
            hpt.Start();
            X = A.Add(B);
            hpt.Stop();
            Console.WriteLine("Cas scitani matic:" + hpt.Duration + " ms");
            timeM.SetElement(12, 0, hpt.Duration);
            fileWrite("plus", X);

            // minus
            hpt.Start();
            X = A.Subtract(B);
            hpt.Stop();
            Console.WriteLine("Cas odecitani matic:" + hpt.Duration + " ms");
            timeM.SetElement(13, 0, hpt.Duration);
            fileWrite("minus", X);

            // arrayTimes
            hpt.Start();
            X = A.ArrayMultiply(B);
            hpt.Stop();
            Console.WriteLine("Cas nasobeni matic poslozkach:" + hpt.Duration + " ms");
            timeM.SetElement(14, 0, hpt.Duration);
            fileWrite("times", X);

            // arrayRightDivide
            hpt.Start();
            X = A.ArrayRightDivide(B);
            hpt.Stop();
            Console.WriteLine("Cas deleni matic poslozkach (Right):" + hpt.Duration + " ms");
            timeM.SetElement(15, 0, hpt.Duration);
            fileWrite("rdivide", X);

            // arrayLeftDivide
            hpt.Start();
            X = A.ArrayLeftDivide(B);
            hpt.Stop();
            Console.WriteLine("Cas deleni matic poslozkach (Left): " + hpt.Duration + " ms");
            timeM.SetElement(16, 0, hpt.Duration);
            fileWrite("ldivide", X);

            // times
            int atimes = 4;
            hpt.Start();
            X = A.Multiply(atimes);
            hpt.Stop();
            Console.WriteLine("Cas nasobeni matice prvkem:" + hpt.Duration + " ms");
            timeM.SetElement(17, 0, hpt.Duration);
            fileWrite("multa", X);

            // timesMatrix
            hpt.Start();
            X = A.Multiply(B);
            hpt.Stop();
            Console.WriteLine("Cas nasobeni matic:" + hpt.Duration + " ms");
            timeM.SetElement(18, 0, hpt.Duration);
            fileWrite("mult", X);

            // inverse
            hpt.Start();
            X = A.Inverse();
            hpt.Stop();
            Console.WriteLine("Cas inverze:" + hpt.Duration + " ms");
            fileWrite("inv", X);
            timeM.SetElement(19, 0, hpt.Duration);

            // rank
            hpt.Start();
            int arank = A.Rank();
            hpt.Stop();
            Console.WriteLine("Cas vypoctu rank:" + hpt.Duration + " ms");
            timeM.SetElement(20, 0, hpt.Duration);
            Console.WriteLine(arank);
            fileWrite("rank", arank);

            // determinant
            hpt.Start();
            double detA = A.Determinant();
            hpt.Stop();
            Console.WriteLine("Cas vypoctu determinantu matice:" + hpt.Duration + " ms");
            timeM.SetElement(21, 0, hpt.Duration);
            fileWrite("det", detA);


            // LU Decomposition
            //exist if singular
            X = nacti("LU");
            if (!X.LUD().IsNonSingular)
            {
                hpt.Start();
                Z = X.LUD().L;
                U = X.LUD().U;
                pole1Da = X.LUD().DoublePivot;
                hpt.Stop();
                Console.WriteLine("Cas vypoctu LU rozkladu:" + hpt.Duration + " ms");
                timeM.SetElement(22, 0, hpt.Duration);
                fileWrite("lu_l", Z);
                fileWrite("lu_u", U);
                fileWrite("lu_p", pole1Da);
            }
            else
            {
                print("Podminky pro LU nebyly splneny");
            }

            // QRDecomposition OK
            hpt.Start();
            X = A.QRD().Q; //ccceconomy size
            //U = A.qr().getH(); R//etreive the Householder vectors from QR factorization
            V = A.QRD().R; //matlab qr(A)
            hpt.Stop();
            Console.WriteLine("Cas QRDecomposition:" + hpt.Duration + " ms");
            timeM.SetElement(23, 0, hpt.Duration);
            //vypis(X.Multiply(V));
            fileWrite("qr_q", X);
            fileWrite("qr_r", V);

            // For a symmetric, positive definite matrix A
            // A = L'*L
            Z = nacti("CHOL");
            if (Z.chol().SPD)
            {
                hpt.Start();
                X = Z.chol().GetL();
                hpt.Stop();
                Console.WriteLine("Cas CholeskyDecomposition(A = L'*L):" + hpt.Duration + " ms");
                timeM.SetElement(24, 0, hpt.Duration);
                //vypis(X.Transpose().Multiply(X));
                fileWrite("chol", X);
            }
            else
            {
                print("Podminky pro chol nebyly splneny");
            }
            //System.out.println("ERROR - jama help(A = L*L') matlab(A = L'*L)");
            //System.out.println("Vynasobenim nedosahneme pozadovane matice");

            // SingularValueDecomposition
            // A = U*S*V'
            hpt.Start();
            X = A.SVD().GetU();
            U = A.SVD().S;
            V = A.SVD().GetV();
            hpt.Stop();
            Console.WriteLine("Cas SingularValueDecomposition:" + hpt.Duration + " ms");
            timeM.SetElement(25, 0, hpt.Duration);
            //vypis(X.Multiply(U).Multiply(V.Transpose()));
            fileWrite("svd_u", X);
            fileWrite("svd_s", U);
            fileWrite("svd_v", V);

            // Eigenvalues and eigenvectors of a real matrix
            // A = V*D*V'
            hpt.Start();
            X = A.Eigen().D;
            U = A.Eigen().GetV();
            pole1Da = A.Eigen().ImagEigenvalues;
            pole1Db = A.Eigen().RealEigenvalues;
            hpt.Stop();
            Console.WriteLine("Cas Eigenvalues and eigenvectors of a real matrix:"
                              + hpt.Duration + " ms");
            timeM.SetElement(26, 0, hpt.Duration);
            
            //vypis(X);//vypis(U);
            //vypis(U.Multiply(X).Multiply(U.Transpose()));
            fileWrite("eig_d", X);
            fileWrite("eig_v", U);
            fileWrite("eig_iv", pole1Da);
            fileWrite("eig_rv", pole1Db);

            timeWrite(timeM, A.RowDimension);
            print("PRESS KEY");
            Console.ReadKey();
        }

        public static void vypis(GeneralMatrix XX)
        {
            if (random == 0)
            {
                for (int i = 0; i < XX.RowDimension; i++)
                {
                    for (int j = 0; j < XX.ColumnDimension; j++)
                    {
                        Console.Write(" " + Math.Round(XX.GetElement(i, j), 2) + "\t");
                        if (j == XX.ColumnDimension - 1)
                        {
                            Console.Write("\n");
                        }
                    }
                }
            }
        }

        public static void fileWrite(String nazew, GeneralMatrix XX)
        {
            StreamWriter theWriter = null;
            try
            {
                theWriter = new StreamWriter(cesta + nazew + "_cs" + XX.RowDimension + ".csv");
                NumberFormatInfo nfi = new CultureInfo("en-US", false).NumberFormat;
                for (int i = 0; i < XX.RowDimension; i++)
                {
                    for (int j = 0; j < XX.ColumnDimension; j++)
                    {
                        theWriter.Write(XX.GetElement(i, j).ToString(nfi) + ";");
                        if (j == XX.ColumnDimension - 1)
                        {
                            theWriter.Write("\n");
                        }
                    }
                }
            }
            finally
            {
                theWriter.Close();
            }
        }


        public static void fileWriteDouble(String nazew, GeneralMatrix XX)
        {
            StreamWriter theWriter = null;
            try
            {
                theWriter = new StreamWriter(cesta + nazew + "_cs" + XX.RowDimension * 2 + ".csv");
                NumberFormatInfo nfi = new CultureInfo("en-US", false).NumberFormat;
                for (int i = 0; i < XX.RowDimension; i++)
                {
                    for (int j = 0; j < XX.ColumnDimension; j++)
                    {
                        theWriter.Write(XX.GetElement(i, j).ToString(nfi) + ";");
                        if (j == XX.ColumnDimension - 1)
                        {
                            theWriter.Write("\n");
                        }
                    }
                }
            }
            finally
            {
                theWriter.Close();
            }
        }


        public static void fileWrite(String nazew, double x)
        {
            StreamWriter theWriter = null;
            try
            {
                theWriter = new StreamWriter(cesta + nazew + "_cs" + A.RowDimension + ".csv");
                NumberFormatInfo nfi = new CultureInfo("en-US", false).NumberFormat;
                theWriter.Write(x.ToString(nfi));
            }
            finally
            {
                theWriter.Close();
            }
        }

        public static void fileWrite(String nazew, double[] pole)
        {
            StreamWriter theWriter = null;
            try
            {
                theWriter = new StreamWriter(cesta + nazew + "_cs" + A.RowDimension + ".csv");
                NumberFormatInfo nfi = new CultureInfo("en-US", false).NumberFormat;
                for (int i = 0; i < pole.Length; i++)
                {
                    theWriter.Write(pole[i].ToString(nfi) + "\n");
                }
            }
            finally
            {
                theWriter.Close();
            }
        }

        public static void timeWrite(GeneralMatrix XX, int size)
        {
            StreamWriter theWriter = null;
            try
            {
                string nazew = "time";
                theWriter = new StreamWriter(cesta + nazew + "_cs" + size + ".csv");
                NumberFormatInfo nfi = new CultureInfo("en-US", false).NumberFormat;
                for (int i = 1; i < XX.RowDimension; i++)
                {
                    theWriter.Write(XX.GetElement(i, 0).ToString(nfi) + "\n");
                }
            }
            finally
            {
                theWriter.Close();
            }
        }

        private static void print(String s)
        {
            Console.WriteLine(s);
        }

        public static GeneralMatrix nacti(String nazev)
        {
            Stream lStream = null;
            Stream lStream2 = null;
            GeneralMatrix XY;

            try
            {
                lStream = new FileStream(cesta + "config.txt", FileMode.Open);
                TextReader lReader = new StreamReader(lStream);

                string lRadek = lReader.ReadLine(); //nacte prvni knicemu                
                string hodnotaS = lReader.ReadLine(); //velikost matice                
                double hodnotaD; // Double.Parse(hodnotaS);                
                hodnotaD = Convert.ToDouble(hodnotaS);
                //Console.WriteLine(hodnotaD);
                lStream.Close();

                string soubor = (@"C:\jama\matice" + nazev + hodnotaD + ".csv");
                Console.WriteLine(soubor);
                XY = new GeneralMatrix(Convert.ToInt32(hodnotaD), Convert.ToInt32(hodnotaD));
                lStream2 = new FileStream(@soubor, FileMode.Open);
                TextReader lReader2 = new StreamReader(lStream2);
                Int32 i = 0;
                Int32 j;

                while ((lRadek = lReader2.ReadLine()) != null)
                {
                    j = 0;
                    foreach (string lPrvek in lRadek.Split(';'))
                    {
                        NumberFormatInfo nfi = new CultureInfo("en-US", false).NumberFormat;
                        double hodnota = Double.Parse(lPrvek, nfi);
                        XY.SetElement(i, j, hodnota);
                        j++;
                    }
                    i++;
                }
                return (XY);
            }
            finally
            {
                lStream.Close();
                lStream2.Close();
            }
        }

        public class HiPerfTimer
        {
            [DllImport("Kernel32.dll")]
            private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);
            [DllImport("Kernel32.dll")]
            private static extern bool QueryPerformanceFrequency(out long lpFrequency);

            private long startTime;
            private long stopTime;
            private long freq;
            /// <summary>
            /// ctor
            /// </summary>
            public HiPerfTimer()
            {
                startTime = 0;
                stopTime = 0;
                freq = 0;
                if (QueryPerformanceFrequency(out freq) == false)
                {
                    throw new Win32Exception(); // timer not supported
                }
            }
            /// <summary>
            /// Start the timer
            /// </summary>
            /// <returns>long - tick count</returns>
            public long Start()
            {
                QueryPerformanceCounter(out startTime);
                return startTime;
            }
            /// <summary>
            /// Stop timer 
            /// </summary>
            /// <returns>long - tick count</returns>
            public long Stop()
            {
                QueryPerformanceCounter(out stopTime);
                return stopTime;
            }
            /// <summary>
            /// Return the duration of the timer (in miliseconds)
            /// </summary>
            /// <returns>double - duration</returns>
            public double Duration
            {
                get
                {
                    return (double)(stopTime - startTime) / (double)(freq /1000);
                }
            }
            /// <summary>
            /// Frequency of timer (no counts in one second on this machine)
            /// </summary>
            ///<returns>long - Frequency</returns>
            public long Frequency
            {
                get
                {
                    QueryPerformanceFrequency(out freq);
                    return freq;
                }
            }
        }
    }
}
